package org.apache.lucene.ocean.util;

import java.io.IOException;
import java.io.Reader;
import java.util.HashMap;
import java.util.Map;

import org.apache.lucene.analysis.Analyzer;
import org.apache.lucene.analysis.Token;
import org.apache.lucene.analysis.TokenStream;
import org.apache.lucene.store.IndexInput;
import org.apache.lucene.util.UnicodeUtil;

/**
 * 
 * 
 */
// TODO: store strings in hash and index by number
public class TokenStreamInput {
  private IndexInput originalInput;
  private int numDocs;
  private FieldInfos[] fieldInfos;
  private static ContextThreadLocal contextThreadLocal = new ContextThreadLocal();

  public TokenStreamInput(IndexInput originalInput) throws IOException {
    this.originalInput = originalInput;
    numDocs = originalInput.readVInt();
    readFieldIndexes();
  }
  
  public Analyzer getAnalyzer(int doc) {
    Context context = getContext();
    context.analyzerImpl.doc = doc;
    return context.analyzerImpl;
  }

  public class AnalyzerImpl extends Analyzer {
    int doc;

    public TokenStream tokenStream(String fieldName, Reader reader) {
      try {
        return getTokenStream(doc, fieldName);
      } catch (IOException ioException) {
        throw new RuntimeException(ioException);
      }
    }
  }
  
  public static class ContextThreadLocal extends ThreadLocal<Context> {
    protected Context initialValue() {
      return new Context();
    }
  }

  public Context getContext() {
    Context context = contextThreadLocal.get();
    if (context.input == null) {
      context.input = (IndexInput) originalInput.clone();
    }
    if (context.analyzerImpl == null) {
      context.analyzerImpl = new AnalyzerImpl();
    }
    return context;
  }
  
  public static class Context {
    private TokenStreamImpl tokenStreamImpl = new TokenStreamImpl();
    private UnicodeUtil.UTF16Result text = new UnicodeUtil.UTF16Result();
    private UnicodeUtil.UTF8Result bytes = new UnicodeUtil.UTF8Result();
    private IndexInput input;
    private AnalyzerImpl analyzerImpl;
  }

  public TokenStream getTokenStream(int doc, String field) throws IOException {
    Context context = getContext();
    FieldInfo fieldInfo = fieldInfos[doc].get(field);
    int position = fieldInfo.position;
    context.input.seek(position);
    TokenStreamImpl tokenStreamImpl = context.tokenStreamImpl;
    tokenStreamImpl.setNum(fieldInfo.num);
    return tokenStreamImpl;
  }

  public static class TokenStreamImpl extends TokenStream {
    int position;
    int num;
    int i;
    private IndexInput input;
    private Context context;

    public TokenStreamImpl() {

    }

    public void setNum(int num) {
      this.num = num;
      i = 0;
    }

    public Token next() throws IOException {
      return next(new Token());
    }

    public Token next(Token token) throws IOException {
      if (i == num)
        return null;
      int length = input.readVInt();
      context.bytes.setLength(length);
      input.readBytes(context.bytes.result, 0, length);
      UnicodeUtil.UTF8toUTF16(context.bytes.result, 0, length, context.text);
      token.setTermBuffer(context.text.result, 0, context.text.length);
      token.setStartOffset(input.readVInt());
      token.setEndOffset(input.readVInt());
      i++;
      return token;
    }
  }

  public static class FieldInfo {
    public int position;
    public int num;
  }

  public static class FieldInfos {
    private Map<String,FieldInfo> map;

    public FieldInfos() {

    }

    public FieldInfo get(String field) {
      return map.get(field);
    }
  }

  private void readFieldIndexes() throws IOException {
    int numFields = originalInput.readVInt();
    Map<String,Integer> map = new HashMap<String,Integer>(numFields);
    Map<Integer,String> otherMap = new HashMap<Integer,String>(numFields);
    for (int x = 0; x < numFields; x++) {
      String name = originalInput.readString();
      map.put(name, x);
      otherMap.put(x, name);
    }
    fieldInfos = new FieldInfos[numDocs];
    for (int x = 0; x < fieldInfos.length; x++) {
      fieldInfos[x] = new FieldInfos();
      int fieldInfoNum = originalInput.readVInt();
      fieldInfos[x].map = new HashMap<String,FieldInfo>();
      for (int y = 0; y < fieldInfoNum; y++) {
        int stringNum = originalInput.readVInt();
        FieldInfo fieldInfo = new FieldInfo();
        fieldInfo.position = originalInput.readVInt();
        fieldInfo.num = originalInput.readVInt();
        String name = otherMap.get(stringNum);
        fieldInfos[x].map.put(name, fieldInfo);
      }
    }
  }
}
